﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
using System.Threading.Tasks;
using Net.Chirash.Twitter.TwitterClient;
using Net.Chirash.Twitter.TwitterClient.DBSyncronize;
using Net.Chirash.Twitter.TwitterDatabase;

namespace Net.Chirash.Twitter.TweetZone.WindowComponents.ViewModels
{
    public abstract class TwitterContainerViewModel<T>
        : ContainerViewModelBase, IDisposable, ITaskObserver<IEnumerable<TwitterData>>
        where T : TwitterData
    {
        #region Constructor

        public TwitterContainerViewModel()
            : this(App.CurrentApp.User)
        {
        }

        public TwitterContainerViewModel(User user)
            : base()
        {
            this.Collection = new ObservableCollection<T>();
            this.LastErrorMessage = new ErrorMessage();
            this.CurrentUser = user;
        }

        #endregion

        #region Public Properties

        #region public ObservableCollection<T> Collection { get; set; }
        private ObservableCollection<T> _Collection { get; set; }
        public ObservableCollection<T> Collection
        {
            get { return _Collection; }
            set { _Collection = value; OnPropertyChanged("Collection"); }
        }
        #endregion
        #region public ITwitterCommand TwitterCommand { get; set; }
        private ITwitterCommand _TwitterCommand { get; set; }
        public ITwitterCommand TwitterCommand
        {
            get { return _TwitterCommand; }
            set { _TwitterCommand = value; OnPropertyChanged("TwitterCommand"); }
        }
        #endregion
        #region public bool IsRefreshingColumn { get; protected set;}
        protected bool _isRefreshingColumn;
        public bool IsRefreshingColumn
        {
            get { return _isRefreshingColumn; }
            protected set { _isRefreshingColumn = value; OnPropertyChanged("IsRefreshingColumn"); }
        }
        #endregion
        #region public bool IsRefreshingTask { get; protected set; }
        private bool _IsRefreshingTask;
        public bool IsRefreshingTask
        {
            get { return _IsRefreshingTask; }
            protected set { _IsRefreshingTask = value; OnPropertyChanged("IsRefreshingTask"); }
        }
        #endregion
        #region public User CurrentUser { get; set; }
        private User _CurrentUser;
        public User CurrentUser
        {
            get { return _CurrentUser; }
            set { _CurrentUser = value; OnPropertyChanged("CurrentUser"); }
        }
        #endregion

        public readonly object LockIsRefreshingColumn = new object();
        public readonly object LockIsRefreshingTask = new object();
        public virtual string ContainerTitle
        {
            get
            {
                return "";
            }
        }

        #endregion

        #region Protected Members

        protected virtual string TaskName
        {
            get { return this.ContainerTitle; }
        }

        protected virtual IEnumerable<T> UpdateDatabaseDataProviding(ITwitterCommand command)
        {
            RefreshArgs<T> args = null;
            try
            {
                InvokeByComposerIfNeeded(new Action(() =>
                {
                    args = CreateRefreshArgs(null);
                }));
            }
            catch (InvalidOperationException ex)
            {
                throw new InvalidOperationException("Dispatcher消失の関係でRefreshArgsを作成することができません");
            }

            args.IsAsyncCall = true;
            args.TwitterCommand = command;
            RefreshDataProviding(args);

            return args.ResultEnumerable;
        }

        protected abstract void UpdateDatabase(TwitterCaching caching, IEnumerable<T> updatelist);

        /// <summary>
        /// Refreshの際に使われるコレクションを取得する関数。Refreshで呼び出されます。結果はe.ResultEnumeratorに設定してください。
        /// </summary>
        /// <param name="e">CreateRefreshArgsで生成されたRefreshArgs</param>
        protected abstract void RefreshDataProviding(RefreshArgs<T> e);

        /// <summary>
        /// RefreshDataProviding関数の引数を生成する関数。非同期処理の関係で、必ずしも生成されたデータがRefreshで使われるとは限りません。
        /// </summary>
        /// <param name="args">引継ぐべきRefreshArgs。nullのときはこの関数でRefreshArgsを作成してください。nullでないときはこのRefreshArgsに然るべき設定をしてください</param>
        /// <returns>設定されたRefreshArgs</returns>
        protected virtual RefreshArgs<T> CreateRefreshArgs(RefreshArgs<T> args)
        {
            RefreshArgs<T> resultArgs = args;
            if (args == null)
                resultArgs = new RefreshArgs<T>();

            resultArgs.CollectionCount = this.Collection.Count;
            resultArgs.TwitterCommand = this.TwitterCommand;
            resultArgs.CurrentUser = this.CurrentUser;
            if (Collection.Count > TweetZone.Properties.Settings.Default.NumberOfStatusesToGet)
            {
                resultArgs.RequiredCount = Collection.Count;
                if (Collection.Count > 0)
                    resultArgs.TopData = Collection.First();
            }
            else
            {
                resultArgs.RequiredCount = TweetZone.Properties.Settings.Default.NumberOfStatusesToGet;
            }
            return resultArgs;
        }

        #endregion

        #region Refresh Methods

        public override void Refresh()
        {
            Refresh(false);
        }

        public void RefreshAsync()
        {
            RefreshAsync(null);
        }

        public override void RefreshAsync(Action<ContainerViewModelBase> completed)
        {
            Refresh(true);

            if (completed != null)
            {
                Task.Factory.StartNew(() =>
                    {
                        while (true)
                        {
                            bool expr = true;
                            lock (LockIsRefreshingColumn)
                            {
                                expr = this.IsRefreshingColumn;
                            }
                            if (!expr) // not refreshing
                                break;

                            System.Threading.Thread.Sleep(100);
                        }
                    })
                    .ContinueWith(new Action<Task>((task) =>
                    {
                        try
                        {
                            InvokeByComposerIfNeeded(() =>
                            {
                                completed(this);
                            });
                        }
                        catch (InvalidOperationException) { }
                    }));
            }
        }

        private void Refresh(bool async)
        {
            lock (LockIsRefreshingColumn)
            {
                if (IsRefreshingColumn)
                    return;
            }

            var args = CreateRefreshArgs(null);
            args.IsAsyncCall = async;

            var gettingFunc
            = new Action(() =>
                {
                    try
                    {
                        lock (LockIsRefreshingColumn)
                        {
                            IsRefreshingColumn = true;
                        }
                        RefreshDataProviding(args);
                    }
                    catch (Exception) { throw; }
                }
            );

            var ApplyingFunc
            = new Action<Task>((task) =>
                {
                    ErrorMessageProvider newProvider = null;
                    try
                    {
#if DEBUG
                        newProvider = new ErrorMessageProvider(new ApplicationException("Debug Assertion"));
#endif
                        if (task.Exception != null)
                            newProvider = new ErrorMessageProvider(task.Exception.InnerException);
                        else
                        {
                            if (args.ResultEnumerable != null)
                            {
                                this.Collection.Clear();

                                foreach (var item in args.ResultEnumerable)
                                    this.Collection.Add(item);
                            }
                        }
                    }
                    catch (NotSupportedException) { }
                    catch (Exception)
                    {
                        throw;
                    }
                    finally
                    {
                        this.LastErrorMessage.Dispatcher.BeginInvoke(new Action(() => this.LastErrorMessage.Provider = newProvider));
                        lock (LockIsRefreshingColumn)
                        {
                            this.IsRefreshingColumn = false;
                        }
                    }
                }
            );//end declation ApplyFunc

            //同期非同期の振り分け
            var processingTask = new Task(gettingFunc);
            processingTask.ContinueWith(new Action<Task>((task) =>
                {
                    try
                    {
                        InvokeByComposerIfNeeded(() => ApplyingFunc(task));
                    }
                    catch (InvalidOperationException)
                    {
                        if (task.Exception != null) //必ず例外チェック
                            Console.WriteLine(ErrorMessageProvider.CreateErrorMessage(task.Exception));
                    }
                }));

            if (async)
            {
                processingTask.Start();
            }
            else
            {
                processingTask.RunSynchronously();
            }
        }

        #endregion

        #region implemention of ITaskObserver

        public override void TaskSubscribe()
        {
            ReceiveTwitterRESTApiEvent();
            ReceiveUserStreams();
        }

        protected void ReceiveTwitterRESTApiEvent()
        {
            this.TaskRegisterSafely(App.CurrentApp.TimerSynchronizer, this.TaskName,
                () => new TwitterPullUpdateTask<T>(taskname: this.TaskName,
                                                    srccmd: TwitterCommandFactory.CreateRemoteTwitterCommand(),
                                                    caching: TwitterCommandFactory.CreateLocalTwitterCommand(),
                                                    getting: new Func<ITwitterCommand, IEnumerable<T>>(UpdateDatabaseDataProviding),
                                                    applying: new Action<TwitterCaching, IEnumerable<T>>(UpdateDatabase)
                                                    ));
        }

        protected void ReceiveUserStreams()
        {
            this.TaskRegisterSafely(App.CurrentApp.ObservingSynchronizer, this.TaskName,
                () => new TwitterPushTask<T>(taskname: this.TaskName));
        }

        protected void ReceiveDeletedInfo()
        {
            var taskname = "Deleted Notifier";
            this.TaskRegisterSafely(App.CurrentApp.ObservingSynchronizer, taskname, () => new TwitterPushTask<DeletedData>(taskname));
        }

        protected void TaskRegisterSafely(CachingSynchronizer sync, string taskname, Func<TaskBase> taskcreateFunc)
        {
            TaskBase task = null;

            if (sync.ExistTask(this.TaskName))
            {
                task = sync.GetTask(this.TaskName);
            }
            else
            {
                task = taskcreateFunc();
                sync.AddTaskIgnoringException(task);
            }

            if (task is IObservable<IEnumerable<TwitterData>>)
            {
                this.unsubscriber.Add((task as IObservable<IEnumerable<TwitterData>>).Subscribe(this));
            }

        }

        public override void UnSubscribe()
        {
            foreach (var item in unsubscriber)
                item.Dispose();
        }

        //TODO : unsubscribeの適切な処理
        private List<IDisposable> unsubscriber = new List<IDisposable>();
        public virtual void OnRefreshStart()
        {
            try
            {
                InvokeByComposerIfNeeded(() =>
                {
                    lock (LockIsRefreshingTask)
                    {
                        this.IsRefreshingTask = true;
                    }
                });
            }
            catch (InvalidOperationException) { }
        }

        public virtual void OnRefreshEnd()
        {
            try
            {
                InvokeByComposerIfNeeded(() =>
                {
                    lock (LockIsRefreshingTask)
                    {
                        this.IsRefreshingTask = false;
                    }
                });
            }
            catch (InvalidOperationException) { }
        }

        public virtual void OnCompleted()
        {
            UnSubscribe();
        }

        public virtual void OnError(Exception error)
        {
            try
            {
                InvokeByComposerIfNeeded(() =>
                {
                    //apply error after completed
                    RefreshAsync((vm) => this.LastErrorMessage.Provider = new ErrorMessageProvider(error));
                });
            }
            catch (InvalidOperationException) { }
        }

        public virtual void OnNext(IEnumerable<TwitterData> value)
        {
            try
            {
                InvokeByComposerIfNeeded(() =>
                {
                    RefreshAsync();
                });
            }
            catch (InvalidOperationException) { }
        }

        #endregion

        public void Dispose()
        {
            GC.SuppressFinalize(this);
            this.UnSubscribe();
        }

        ~TwitterContainerViewModel()
        {
            Dispose();
        }

    }

    public class RefreshArgs<T>
        where T : TwitterData
    {
        public T TopData;
        public int CollectionCount;
        public User CurrentUser;
        public ITwitterCommand TwitterCommand;
        public int RequiredCount;
        public bool IsAsyncCall;
        public IEnumerable<T> ResultEnumerable;
    }

}
